PLAYER_LIST = {
'ps3': ['ANGRY-GaNgStAs', 'The-angry-teddy'],
'360': ['EBK505', 'Element077'],
'pc':  ['MetalSpuddy', 'trivas'],
}
PLAYER_FILE = '.bfbcs.players' #file which contains list of players.  Overrides defaults specified above

SORT_COL    = 0     # 0=time, 1=kills, 2=kpm
MIN_SECONDS = 60*60 # must have used weapon for at least this long, in seconds
ENTRIES     = 5     # number of weapons displayed

#normal users shouldn't edit anything below here
DISPLAY_WEAPONS = False
DISPLAY_PRIMARY = False
DISPLAY_SECONDARY = False
DISPLAY_VEHICLES = False
DISPLAY_SCORES = False
COLLATE_REPORT = False
REPORT_SORT = None
QUIET = False
PATH = None
VEHICLE_KDR = False
EPM = False
EPM_ENTRIES = None
PER_SESSION = False
DOGTAGS = False
DOGTAG_COLLATE = False
OVERALL     = 'Overall'
ASSAULT     = 'Assault'
ENGINEER    = 'Engineer'
MEDIC       = 'Medic'
RECON       = 'Recon'
ALL_KIT     = 'All Kit Only'
PRIMARY     = 'PRIMARY'
SECONDARY   = 'SECONDARY'
EQUIP       = 'EQUIP'
DISPLAY = []
PLAYERS = {}

import urllib, urllib2, sys, os
try:
	import json.loads as jsonify
except ImportError:
	sys.stderr.write("WARNING: You should upgrade to at least python version 2.6 for safe JSON conversion\n")
	def jsonify(s):
		conversions = {
			'false' : 'False',
			'true'  : 'True',
			'null'  : 'None',
		}
		for j in conversions:
			s = s.replace(j, conversions[j])
		return eval(s)

player_from_lower = {}
def load_players(filename):
	for system, player in (x.strip().split(":") for x in open(filename) if ":" in x):
		try:
			PLAYERS[system].append(player)
		except KeyError:
			PLAYERS[system] = [player]
		player_from_lower[player.lower()] = player

if len(sys.argv) > 1:
	prog_name = os.path.basename(sys.argv.pop(0))
	def next_arg():
		return sys.argv.pop(0).lower()
	while sys.argv:
		arg = next_arg()
		if arg == "/?":
			print """\
USAGE: %s [System:Name]* [/F file] [/KPM|TIME|KILLS] [/O] [/A] [/E] [/M] [/R] [/W] [/S] [/Q] [/H[S] rows] [/V] [/D[C]] [/C[P|O|A|G|F|E]] [/N rows] [/T mins] [/P path] 

	System      ps3, 360 or pc
	Name        player name
	*           You can specifiy more than one player.
	            Example: bfbcs.py ps3:ANGRY-GaNgStAs 360:EBK505
	            If you don't specify a player on the command line then
	            the default player list will be used.
	
	/F file     File containing player list      
	
	/O          Display Overall
	/A          Display Assault
	/E          Display Engineer
	/M          Display Medic
	/R          Display Recon

	/W[PSV]     Display weapons
	            If [P] then show Primary weapons
	            If [S] then show Secondary weapons
	            If [V] then show vehicles
	            
	/S          Display scores:
	            OPM, Ace, Gold Squad, On-foot, EPM
	
	/H[S] rows  Display 'rows' history entries (or all if rows = 0)
	            as they affect overall player stats.
	            If [S] is used, displays per-session stats instead.
	
	/D[C]       Displays Dogtags held between players
	            If [C] used collates into versus table
	
	/V          Displays KDR modified by vehicle use
	

	/KPM        Sort weapons by KPM
	/TIME       Sort weapons by Time
	/KILLS      Sort weapons by Kills
	
	/N rows     Number of weapon entries to display

	/T mins     Only consider weapons with at least this many minutes use

	/Cx         Collate scores (and sort on x if specified)


	/P path	    For debugging.  Displays the contents of the player data.
	            Example:
	                bfbcs.py /P / 
	                bfbcs.py /P /general 
	                bfbcs.py /P /general/games


	Note:      If you do not specify something to display (/W /S /H /D /V) 
	           then /WP will be used by default
	           
""" % prog_name
			sys.exit(0)
		elif arg == "/kpm":
			SORT_COL = 2
		elif arg == "/time":
			SORT_COL = 0
		elif arg == "/kills":
			SORT_COL = 1
		elif arg == "/o":
			DISPLAY.append(OVERALL)
		elif arg == "/a":
			DISPLAY.append(ASSAULT)
		elif arg == "/e":
			DISPLAY.append(ENGINEER)
		elif arg == "/m":
			DISPLAY.append(MEDIC)
		elif arg == "/r":
			DISPLAY.append(RECON)
		elif arg == "/s":
			DISPLAY_SCORES = True
		elif arg.startswith("/w"):
			DISPLAY_WEAPONS = True
			options = arg[2:]
			if not options or "p" in options:
				DISPLAY_PRIMARY = True
			if "s" in options:
				DISPLAY_SECONDARY = True
			if "v" in options:
				DISPLAY_VEHICLES = True	
		elif arg.startswith("/d"):
			DOGTAGS = True
			if arg.endswith("c"):
				DOGTAG_COLLATE = True
		elif arg == "/v":
			VEHICLE_KDR = True
		elif arg.startswith("/c"):
			if len(arg) > 2:
				try:
					REPORT_SORT = {"p": 0, "o": 1, "a":2, "g": 3, "f": 4, "e": 5}[arg[2]]
				except KeyError:
					pass
			COLLATE_REPORT = True
		elif arg.startswith("/h"):
			EPM = True
			if arg[-1] == "s":
				PER_SESSION = True
			EPM_ENTRIES = int(next_arg())
			if not EPM_ENTRIES:
				EPM_ENTRIES = None
		elif arg == "/n":
			ENTRIES = int(next_arg())
		elif arg == "/t":
			MIN_SECONDS = int(next_arg()) * 60
		elif arg == "/p":
			PATH = next_arg()
		elif arg == "/f":
			load_players(next_arg())
		elif ":" in arg:
			system, player = arg.split(":")
			try:
				PLAYERS[system].append(player)
			except KeyError:
				PLAYERS[system] = [player]

if not DISPLAY:
	DISPLAY = [OVERALL] #, ASSAULT, ENGINEER, MEDIC, RECON, ALL_KIT]
if not DISPLAY_WEAPONS and not DISPLAY_SCORES and not EPM and not VEHICLE_KDR and not DOGTAGS:
	DISPLAY_WEAPONS = True
	DISPLAY_PRIMARY = True
QUIET = not(DISPLAY_WEAPONS or VEHICLE_KDR or EPM)
if DISPLAY_SCORES and not COLLATE_REPORT:
	QUIET = False
if DOGTAGS and not DOGTAG_COLLATE:
	QUIET = False
if not PLAYERS:
	if os.path.exists(PLAYER_FILE):
		load_players(PLAYER_FILE)
	else:
		PLAYERS = PLAYER_LIST


SITE = "http://api.bfbcs.com/api/%s?players=%s&fields=all"
if DOGTAGS:
	SITE += ",dogtags"
EPM_URL = 'http://bfbcs.com/stats_%s/%s/history'



VIETNAM = 'Vietnam'

WEAPONS = {
'mcs': ('870 Combat', ALL_KIT, PRIMARY),
'9a91': ('9A-91 Avtomat', ENGINEER, PRIMARY),
'aks74u': ('AKS-74U Krinkov', ENGINEER, PRIMARY),
'an94': ('AN-94 Abakan', ASSAULT, PRIMARY),
'aek': ('AEK-971 Vintovka', ASSAULT, PRIMARY),
'f2000': ('F2000 Assault', ASSAULT, PRIMARY),
'g3': ('G3', ALL_KIT, PRIMARY),
'gol': ('GOL Sniper Magnum', RECON, PRIMARY),
'm1': ('M1', ALL_KIT, PRIMARY),
'mk14ebr': ('M14', ALL_KIT, PRIMARY),
'm16': ('M16A2', ASSAULT, PRIMARY),
'm16k': ('M16A2 - SPECACT', ASSAULT, PRIMARY),
'm24': ('M24 Sniper', RECON, PRIMARY),
'm249': ('M249 Saw', MEDIC, PRIMARY),
'm416': ('M416', ASSAULT, PRIMARY),
'm60': ('M60 LMG', MEDIC, PRIMARY),
'm9': ('M9 Pistol', ALL_KIT, SECONDARY),
'm93r': ('M93R Burst', ALL_KIT, SECONDARY),
'm95k': ('M95 - SPECACT', RECON, PRIMARY),
'm95': ('M95 Sniper', RECON, PRIMARY),
'mg3': ('MG3', MEDIC, PRIMARY),
'mg3k': ('MG3 - SPECACT', MEDIC, PRIMARY),
'mg36': ('MG36', MEDIC, PRIMARY),
'mp412': ('MP-412 Rex', ALL_KIT, SECONDARY),
'mp443': ('MP-443 Grach', ALL_KIT, SECONDARY),
'n2k': ('NEOSTEAD 2000 Combat', ALL_KIT, PRIMARY),
'pkm': ('PKM LMG', MEDIC, PRIMARY),
'pp2': ('PP-2000 Avtomat', ENGINEER, PRIMARY),
's12k': ('Saiga 20k Semi', ALL_KIT, PRIMARY),
'scar': ('SCAR-L Carbine', ENGINEER, PRIMARY),
'spas12': ('SPAS-12 Combat', ALL_KIT, PRIMARY),
'aug': ('Stg.77 AUG', ASSAULT, PRIMARY),
'sv98': ('SV98 Snaiperskaya', RECON, PRIMARY),
'svu': ('SVU Snaiperskaya Short', RECON, PRIMARY),
'qju88': ('Type 88 LMG', MEDIC, PRIMARY),
'qby88': ('Type 88 Sniper C', RECON, PRIMARY),
'qbu88': ('Type 88 Sniper C', RECON, PRIMARY),
'umpk': ('UMP - SPECACT', ENGINEER, PRIMARY),
'ump': ('UMP-45', ENGINEER, PRIMARY),
'u12': ('USAS-12 Auto', ALL_KIT, PRIMARY),
'uzi': ('UZI', ENGINEER, PRIMARY),
'vss': ('VSS Snaiperskaya Special', RECON, PRIMARY),
'm1911': ('WWII M1911 .45', ALL_KIT, SECONDARY),
'm1a1': ('WWII M1A1 Thompson', ALL_KIT, PRIMARY),
'xm8c': ('XM8 Compact', ENGINEER, PRIMARY),
'xm8lmg': ('XM8 LMG', MEDIC, PRIMARY),
'xm8': ('XM8 Prototype', ASSAULT, PRIMARY),
'ak47v': (VIETNAM, VIETNAM, VIETNAM),
'deml': (VIETNAM, VIETNAM, VIETNAM),
'm14v': (VIETNAM, VIETNAM, VIETNAM),
'm16a1v': (VIETNAM, VIETNAM, VIETNAM),
'm21v': (VIETNAM, VIETNAM, VIETNAM),
'm2v': (VIETNAM, VIETNAM, VIETNAM),
'm40v': (VIETNAM, VIETNAM, VIETNAM),
'm60v': (VIETNAM, VIETNAM, VIETNAM),
'mac10v': (VIETNAM, VIETNAM, VIETNAM),
'ppshv': (VIETNAM, VIETNAM, VIETNAM),
'rpkv': (VIETNAM, VIETNAM, VIETNAM),
'svdv': (VIETNAM, VIETNAM, VIETNAM),
'tt33v': (VIETNAM, VIETNAM, VIETNAM),
'uziv': (VIETNAM, VIETNAM, VIETNAM),
'xm22v': (VIETNAM, VIETNAM, VIETNAM),
'm136': ('M136 AT4', ENGINEER, EQUIP),
'mots': ('Motion Sensor', RECON, EQUIP),
'atm': ('Anti-Tank Mine', ENGINEER, EQUIP),
'40mmgl': ('40MM Grenade', ASSAULT, EQUIP),
'trad': ('Tracer Dart Gun', ALL_KIT, EQUIP),
'medk': ('Medic Kit', MEDIC, EQUIP),
'rept': ('Repair Tool', ENGINEER, EQUIP),
'rpg7': ('RPG-7 AT', ENGINEER, EQUIP),
'smol': ('40MM Smoke Launcher', ASSAULT, EQUIP),
'40mmsg': ('40MM Shotgun', ASSAULT, EQUIP),
'ammb': ('Ammo Box', ASSAULT, EQUIP),
'm2cg': ('M2 Carl Gustav AT', ENGINEER, EQUIP),
'knv': ('Combat Knife', ALL_KIT, EQUIP),
'mst': ('Mortar Strike', RECON, EQUIP),
'defi': ('Defibrillator', MEDIC, EQUIP),
'hgr': ('Hand Grenade', ALL_KIT, EQUIP),
'c4': ('C4 Explosive', RECON, EQUIP),
}

def seconds_from_time(t):
	h, t = t.split('h ')
	m, t = t.split('m ')
	s, t = t.split('s')
	h, m, s = (int(x) for x in (h, m, s))
	return h * 60 * 60 + m * 60 + s

INF = 999.0
def safe_float_div(x, y):
	if y == 0:
		if x == 0:
			return 0.0
		else:
			return INF
	else:
		return float(x) / y

stats = {}
epm = {}

for platform in PLAYERS:
	try:
		data = [x for x in urllib2.urlopen(SITE % (platform, ','.join(PLAYERS[platform]).replace(" ", "%20")))][0]
	except:
		sys.stderr.write("Error fetching data.  Please try again later\n")
		sys.exit(1)
	data = jsonify(data)
	for i, player in enumerate(data['players']):
		player_name = player['name'].lower()
		stats[(platform, [x for x in PLAYERS[platform] if x.lower() == player_name][0])] = player
	if EPM:
		for player_name in PLAYERS[platform]:
			url = EPM_URL % (platform, player_name.replace(" ", "%20"))
			values = {'mode' : 'all',
					  'request' : 'gethistory'
					  }
			try:
				epm_data = urllib.urlencode(values)
				req = urllib2.Request(url, epm_data)
				response = urllib2.urlopen(req)
				epm_data = response.read()
			except:
				sys.stderr.write("Error fetching data.  Please try again later\n")
				sys.exit(1)
			epm_data = jsonify(epm_data)['hist']
			history = []
			if PER_SESSION:
				t, k, d = 'time_diff', 'kills_diff', 'deaths_diff'
				old_wins = old_losses = old_obj = 0
			else:
				t, k, d = 'time', 'kills', 'deaths'
			for snapshot in sorted(epm_data):
				wins = snapshot['vals']['wins']
				if type(wins) == dict:
					wins = float(wins['value'])
				losses = snapshot['vals']['losses']
				if type(losses) == dict:
					losses = float(losses['value'])
				obj = snapshot['vals']['sc_objective']
				if type(obj) == dict:
					obj = float(obj['value'])
				history.append([
					snapshot['url'], 
					snapshot['date'], 
					seconds_from_time(snapshot[t]), 
					int(snapshot[k].replace(" ","")), 
					int(snapshot[d].replace(" ","")),
					wins,
					losses,
					obj,
					])
				if PER_SESSION:
					history[-1][5] -= old_wins
					history[-1][6] -= old_losses
					history[-1][7] -= old_obj
					old_wins = wins
					old_losses = losses
					old_obj = obj
			history.sort()
			if EPM_ENTRIES is not None:
				epm[(platform, player_name)] = history[-EPM_ENTRIES:]
			else:
				epm[(platform, player_name)] = history
		del epm_data
		
def format_weapon_info(w):
	time, kills, kpm, ofkpm, weapon = w
	try:
		weapon = WEAPONS[weapon][0]
	except:
		pass
	hours = int(time/3600)
	mins = ((time/3600) - hours) * 60
	time = "%02dh%02dm" % (hours, mins)
	if ofkpm == -1:
		ofkpm = "-"
	else:
		ofkpm = "%0.02f" % ofkpm
	return "%-26s%7s   K: %5s   KPM: %5s    Foot KPM: %5s" % (weapon, time, "%d" % kills, "%0.02f" % kpm, ofkpm)

report = []
dogtag_versus = {}
for collection in DISPLAY:
	if DISPLAY_WEAPONS:
		print
		print
		print
		print collection
	for platform, player_name in sorted(stats):
		player_data = stats[(platform, player_name)]
		def get_path(path):
			cd = player_data
			path = path.split("/")
			if len(path) > 1 or path[0] != "":
				for d in path:
					try:
						cd = cd[d]
					except KeyError:
						pass
			return cd
		if PATH:
			info = get_path(PATH)
			if type(info) == dict:
				print list(info)
			else:
				print info
		total_time = player_data['time']
		best = []
		vehicle_time = vehicle_kills = 0
		for vehicle in player_data['vehicles']:
			v = player_data['vehicles'][vehicle]
			vehicle_time += v['seconds']
			vehicle_kills += v['kills']
			if DISPLAY_VEHICLES and 'kills' in v and 'seconds' in v:
				kills, time = v['kills'], float(v['seconds'])
				if time > MIN_SECONDS and kills > 0:
					best.append((time, kills, 60*kills/time, -1, v['name']))
		on_foot = safe_float_div(total_time - vehicle_time,  total_time)
		uav_kills = get_path('/vehicles/uav/kills')
		total_kills = get_path('/kills')
		total_deaths = get_path('/deaths')
		
		if DISPLAY_WEAPONS:
			print
			print
			print "%-64sOn-Foot: %0.02f%%" % (player_data['name'], (on_foot * 100))
		elif not QUIET:
			print
			print
			print player_data['name']
		weapons = dict(player_data['gadgets'].items() + player_data['weapons'].items())
		for weapon in weapons:
			if collection in (OVERALL, WEAPONS[weapon][1]) and WEAPONS[weapon][1] != VIETNAM and ((WEAPONS[weapon][2] == PRIMARY and DISPLAY_PRIMARY) or (WEAPONS[weapon][2] != PRIMARY and DISPLAY_SECONDARY)):
				weapon_stats = weapons[weapon]
				if 'kills' in weapon_stats and 'seconds' in weapon_stats:
					kills, time = weapon_stats['kills'], float(weapon_stats['seconds'])
					if time > MIN_SECONDS and kills > 0:
						best.append((time, kills, 60*kills/time, 60*safe_float_div(kills, time*on_foot), weapon_stats['name']))
		key = SORT_COL
		best = reversed([x[1] for x in sorted(((x[key], x) for x in best))])
		best = list(best)
		if DISPLAY_WEAPONS:
			for i, weapon in enumerate(best[:ENTRIES]):
				print "%2s" % str(i+1), format_weapon_info(weapon)
		if collection == OVERALL:
			if DISPLAY_SCORES:
				objective_score = (100*player_data['general']['sc_objective']/float(player_data['general']['sc_general']))
				objective_spg = safe_float_div(60 * get_path('general/sc_objective'), total_time)
				aces = [x['count'] for x in player_data['pins'] if x['name'] == 'Ace Pin'][0]
				gold = [x['count'] for x in player_data['pins'] if x['name'] == 'Gold Squad Pin'][0]
				aces_score = 100 * safe_float_div(aces, get_path('/general/games'))
				gold_score = 100 * safe_float_div(gold, get_path('/general/games'))
				engagement_score = (safe_float_div((player_data['kills'] + player_data['deaths']), (total_time / 60)))
				if COLLATE_REPORT:
					report.append((player_data['name'], objective_spg, aces_score, gold_score, on_foot, engagement_score))
				else:
					print
					print "%-25s%6s" % ("Objective per Min:", "%.2f" % objective_spg)
					print "%-25s%6s%%" % ("Ace Pins:", "%.2f" % aces_score)
					print "%-25s%6s%%" % ("Gold Squads:", "%.2f" % gold_score)
					print "%-25s%6s%%" % ("Foot (non-vehicle):", "%.2f" % (100 * on_foot))
					print "%-25s%6s" % ("Engagements per Min.:", "%.2f" % engagement_score)
			if VEHICLE_KDR:
				print 
				print "Overall KDR:     %.2f" % safe_float_div(total_kills, total_deaths)
				print "No UAV KDR:      %.2f" % safe_float_div(total_kills-uav_kills, total_deaths)
				print "No Vehicle KDR:  %.2f" % safe_float_div(total_kills-vehicle_kills, total_deaths)
			if DOGTAGS:
				lplayer = player_name.lower()
				opponents = [x.lower() for x in PLAYERS[platform] if x.lower() != lplayer]
				dogtags = get_path('/dogtags/players')
				dogtag_report = []
				for dogtag in dogtags:
					if dogtag['name'].lower() in opponents:
						dogtag_report.append((dogtag['name'], dogtag['gold']+dogtag['silv']+dogtag['bron']))
						if lplayer not in dogtag_versus:
							dogtag_versus[lplayer] = {}
						dogtag_versus[lplayer][dogtag['name'].lower()] = dogtag['gold']+dogtag['silv']+dogtag['bron']
							
						
				if not DOGTAG_COLLATE:
					print
					print "Dogtags:"
					if dogtag_report:
						for opponent, tags in dogtag_report:
							print "%-20s%d" % (opponent, tags)
					else:
						print "None"
						
			if EPM:
				print
				history_report = [("Date", "Kills", "Deaths", "KDR", "KPM", "DPM", "EPM", "OPM", "W/L")]
				for url, date, time, kills, deaths, wins, losses, objective in epm[(platform, player_name)]:
					if PER_SESSION:
						win_loss = "%d/%d" % (wins, losses)
					else:
						win_loss = "%.2f" % safe_float_div(wins, losses)
					history_report.append((
						("%s" % date)[:10], 
						"%d" % kills, 
						"%d" % deaths, 
						"%.2f" % safe_float_div(kills, deaths),
						"%.2f" % (60 * safe_float_div(kills, time)),
						"%.2f" % (60 * safe_float_div(deaths, time)),
						"%.2f" % (60 * safe_float_div(kills + deaths, time)),
						"%.2f" % (60 * safe_float_div(objective, time)),
						"%s" % win_loss,
						))
				for row in history_report:
					print "%-10s%8s%8s%8s%9s%9s%9s%9s%8s" % row

if COLLATE_REPORT:
	print
	print
	if REPORT_SORT is not None:
		report = [(x[REPORT_SORT], x) for x in report]
		report.sort()
		if REPORT_SORT != 0:
			report.reverse()
		report = [x[1] for x in report]
	report = [(x[0], "%.2f" % x[1], "%.2f" % x[2], "%.2f" % x[3], "%.2f" % (100 * x[4]), "%.2f" % x[5]) for x in report]
	report = [
	("Player", "Objective", "Aces %", "Gold", "Foot %", "Engagements"),
	("", "per Min", "", "Squad %", "", "per Minute"),
	("","","","","",""),
	] + report
	for row in report:
		print "%-22s%10s%10s%11s%11s%14s" % row

if DOGTAG_COLLATE:
	print
	print
	print "Dogtags:"
	for player in sorted(dogtag_versus):
		for opponent in sorted(dogtag_versus[player]):
			tags = dogtag_versus[player][opponent]
			if opponent in dogtag_versus and player in dogtag_versus[opponent]:
				opp_tags = dogtag_versus[opponent][player]
			else:
				opp_tags = 0
			if tags >= opp_tags:
				print "%-20s%4s   %-4s%20s" % (player_from_lower[player], str(tags), str(opp_tags), player_from_lower[opponent])